package algorithm;

import java.util.Random;

/**
 * @author gabriella
 *
 */
public class Rule1Cube implements Algorithm {
	
	protected int n, n1, n2, nvert, S, R, T, P;
	protected byte l[][][];
	protected int Sum[][][];
	protected boolean bPeriodic = false;
	protected int C = 0; // number of cooperating individuals
	/**		
	 * @param n 
	 * @param S parameter of the simulation
	 * @param R parameter of the simulation
	 * @param T parameter of the simulation
	 * @param P parameter of the simulation
	 * @param periodic 
	 */
	public Rule1Cube(int n, int S, int R, int T,int P, boolean periodic) {
		this.n = n;
		this.S = S;
		this.R = R;
		this.T = T;
		this.P = P;
		//P = 0;
		bPeriodic = periodic;
		n1 = n + 1;
		n2 = n + 2;
		Sum = new int[n2][n2][n2];
		l = new byte[n2][n2][n2];
		nvert = n * n * n;
	}

	/** 
	 * @see algorithm.Algorithm#cycle(byte[])
	 */
	@Override
	public void cycle(byte[] L) {
		
		for (int i = 0; i < n2; i++)
			for (int j = 0; j < n2; j++)
				for (int k = 0; k < n2; k++)
					Sum[i][j][k] = 0;
		
		// make a "3D" copy of L 
		int p = 0;
		for (int i = 1; i < n1; i++)
			for (int j = 1; j < n1; j++)
				for (int k = 1; k < n1; k++)
					l[i][j][k] = L[p++];
		
		if (bPeriodic) { // periodic
			
			// set periodic neighbors
			for (int i = 1; i < n1; i++)
				for (int j = 1; j < n1; j++) {
					l[i][j][0] = l[i][j][n];
					l[i][j][n1] = l[i][j][1];
				}
			for (int i = 1; i < n1; i++)
				for (int k = 1; k < n1; k++) {
					l[i][0][k] = l[i][n][k];
					l[i][n1][k] = l[i][1][k];
				}
			for (int j = 1; j < n1; j++)
				for (int k = 1; k < n1; k++) {
					l[0][j][k] = l[n][j][k];
					l[n1][j][k] = l[1][j][k];
				}
			
			// apply the rule
			for (int i = 1; i < n1; i++)
				for (int j = 1; j < n1; j++)
					for (int k = 1; k < n1; k++) {
						if (l[i][j][k] == 1) { // C
							Sum[i][j][k] += l[i + 1][j][k] == 1 ? R : P;
							Sum[i][j][k] += l[i - 1][j][k] == 1 ? R : P;
							Sum[i][j][k] += l[i][j + 1][k] == 1 ? R : P;
							Sum[i][j][k] += l[i][j - 1][k] == 1 ? R : P;
							Sum[i][j][k] += l[i][j][k + 1] == 1 ? R : P;
							Sum[i][j][k] += l[i][j][k - 1] == 1 ? R : P;
						}
						else { // D
							Sum[i][j][k] += l[i + 1][j][k] == 1 ? T : S;
							Sum[i][j][k] += l[i - 1][j][k] == 1 ? T : S;
							Sum[i][j][k] += l[i][j + 1][k] == 1 ? T : S;
							Sum[i][j][k] += l[i][j - 1][k] == 1 ? T : S;
							Sum[i][j][k] += l[i][j][k + 1] == 1 ? T : S;
							Sum[i][j][k] += l[i][j][k - 1] == 1 ? T : S;
						}
					}
			
			// set periodic sums
			for (int i = 1; i < n1; i++)
				for (int j = 1; j < n1; j++) {
					Sum[i][j][0] = Sum[i][j][n];
					Sum[i][j][n1] = Sum[i][j][1];
				}
			for (int i = 1; i < n1; i++)
				for (int k = 1; k < n1; k++) {
					Sum[i][0][k] = Sum[i][n][k];
					Sum[i][n1][k] = Sum[i][1][k];
				}
			for (int j = 1; j < n1; j++)
				for (int k = 1; k < n1; k++) {
					Sum[0][j][k] = Sum[n][j][k];
					Sum[n1][j][k] = Sum[1][j][k];
				}			
		}
		
		else { // not periodic
			// apply the rule
			for (int i = 1; i < n1; i++)
				for (int j = 1; j < n1; j++)
					for (int k = 1; k < n1; k++) {
						if (l[i][j][k] == 1) { // C
							if (i < n) Sum[i][j][k] += l[i + 1][j][k] == 1 ? R : P;
							if (i > 1) Sum[i][j][k] += l[i - 1][j][k] == 1 ? R : P;
							if (j < n) Sum[i][j][k] += l[i][j + 1][k] == 1 ? R : P;
							if (j > 1) Sum[i][j][k] += l[i][j - 1][k] == 1 ? R : P;
							if (k < n) Sum[i][j][k] += l[i][j][k + 1] == 1 ? R : P;
							if (k > 1) Sum[i][j][k] += l[i][j][k - 1] == 1 ? R : P;
						} else { // D
							if (i < n) Sum[i][j][k] += l[i + 1][j][k] == 1 ? T : S;
							if (i > 1) Sum[i][j][k] += l[i - 1][j][k] == 1 ? T : S;
							if (j < n) Sum[i][j][k] += l[i][j + 1][k] == 1 ? T : S;
							if (j > 1) Sum[i][j][k] += l[i][j - 1][k] == 1 ? T : S;
							if (k < n) Sum[i][j][k] += l[i][j][k + 1] == 1 ? T : S;
							if (k > 1) Sum[i][j][k] += l[i][j][k - 1] == 1 ? T : S;
						}
					}

		}
		
		// change strategy, if needed
		p = 0;
		C = 0;
		for (int i = 1; i < n1; i++)
			for (int j = 1; j < n1; j++)
				for (int k = 1; k < n1; k++) {
					L[p] = getStrategy(i,j,k);
					if (L[p] == 1) C++;
					p++;
				}
					
	}

	/**
	 * @see algorithm.Algorithm#init(byte[], int[])
	 */
	@Override
	public byte[] init() {
		byte L[] = new byte[nvert];

		Random generator = new Random();

		int def = generator.nextInt(nvert - 2) + 1;

		if (def < nvert / 2) { // we generate defectors
			C = nvert;
			for (int i = 0; i < nvert; i++) {

				L[i] = 1;
			}
			for (int i = 0; i < def; i++) {

				int rand = generator.nextInt(nvert);

				while (L[rand] != 1) {

					rand = generator.nextInt(nvert);
				}
				L[rand] = 0;
				C--;
			}
		} else { // we generate cooperators
			C = 0;
			for (int i = 0; i < nvert; i++) {

				L[i] = 0;
			}
			for (int i = 0; i < nvert-def; i++) {

				int rand = generator.nextInt(nvert);

				while (L[rand] != 0) {

					rand = generator.nextInt(nvert);
				}
				L[rand] = 1;
				C++;
			}
		}
		
		return L;
	}
	
	// selects best strategy based on neighbour's sums
	protected byte getStrategy(int i, int j, int k) {
		byte strategy = l[i][j][k];
		int max = Sum[i][j][k];
		if (bPeriodic) {
			// check the neighbours
			if (Sum[i + 1][j][k] > max) { strategy = l[i + 1][j][k]; max = Sum[i + 1][j][k]; }
			if (Sum[i - 1][j][k] > max) { strategy = l[i - 1][j][k]; max = Sum[i - 1][j][k]; }
			if (Sum[i][j + 1][k] > max) { strategy = l[i][j + 1][k]; max = Sum[i][j + 1][k]; }
			if (Sum[i][j - 1][k] > max) { strategy = l[i][j - 1][k]; max = Sum[i][j - 1][k]; }
			if (Sum[i][j][k + 1] > max) { strategy = l[i][j][k + 1]; max = Sum[i][j][k + 1]; }
			if (Sum[i][j][k - 1] > max) { strategy = l[i][j][k - 1]; max = Sum[i][j][k - 1]; }
		}
		else {
			if (i < n && Sum[i + 1][j][k] > max) { strategy = l[i + 1][j][k]; max = Sum[i + 1][j][k]; }
			if (i > 1 && Sum[i - 1][j][k] > max) { strategy = l[i - 1][j][k]; max = Sum[i - 1][j][k]; }
			if (j < n && Sum[i][j + 1][k] > max) { strategy = l[i][j + 1][k]; max = Sum[i][j + 1][k]; }
			if (j > 1 && Sum[i][j - 1][k] > max) { strategy = l[i][j - 1][k]; max = Sum[i][j - 1][k]; }
			if (k < n && Sum[i][j][k + 1] > max) { strategy = l[i][j][k + 1]; max = Sum[i][j][k + 1]; }
			if (k > 1 && Sum[i][j][k - 1] > max) { strategy = l[i][j][k - 1]; max = Sum[i][j][k - 1]; }
		}
		return strategy;
	}

	@Override
	public int getC() {
		return C;
	}

	@Override
	public int getD() {
		return nvert - C;
	}

}
